package pl;

import javacard.framework.Util;

public abstract class BERTLV {

	boolean emptyTLV;
	short numDeletedTLVs;
	short numTLVs;
	BERTag theBERTag;
	byte[] theValue;	
	short valueLength;
	
	short valueOffset;
	
	protected BERTLV()
	{
		this.emptyTLV = true;
		this.numDeletedTLVs = 0;
		this.theBERTag = null;
		this.valueLength = 0;
	}
	
	public abstract short init(byte[] bArray, short bOff, short bLen);
	
	public short getLength() 
	{
		return valueLength;
	}
	
	public static BERTLV getInstance(byte[] bArray, short bOff, short bLen)
	{
		BERTag berTag;
		BERTLV tlv = null;
		
		if (bArray == null) 
			throw new NullPointerException();	
		else
		{
			if (verifyFormat(bArray,  bOff,  bLen))
			{
				berTag = BERTag.getInstance(bArray, bOff);
				if (berTag.isConstructed())
				{
					tlv = new ConstructedBERTLV((short)12);
					// get length -> 
				}
				else
				{
					tlv = new PrimitiveBERTLV(bLen);
				}		
				tlv.init(bArray, bOff, bLen);
			}
		}
		return tlv;
	}
	
	// TODO: Test
	public static short getLength(byte[] berTLVArray, short bOff) 
	{
		if (berTLVArray == null) throw new NullPointerException();	
		
		if ((short)(berTLVArray.length) < bOff)
		{
			// tag
			if ((berTLVArray[bOff] & 0x1F) == 0x1F) 
				bOff += 2;  // 2 b
			else
				bOff++; //  1 b
			
			// length //9F 66 81 00 =?
			short checkBit = (short) (berTLVArray[bOff] & 0x80);
			
			if (checkBit == 0x00)
			{
				return (short)berTLVArray[(byte)(bOff)];
			}
			else
			{
				checkBit = (short) (berTLVArray[bOff] & 0x03);
				bOff++;
				switch (checkBit)
				{
					case 1:
						// 81 81
						if ((berTLVArray[(short)bOff] & 0x80) == 0x80) 
							return (short) (berTLVArray[(short)(bOff)] & 0x00FF);
						else 
							throw new NullPointerException();	
						
					case 2:
						return MediatorFE.byteArrayToShort(
								new byte[] {berTLVArray[(short)(bOff + 1)], berTLVArray[(short)(bOff)]});
					default:
						throw new NullPointerException();	
				}
			}
		}
		else 
			throw new ArrayIndexOutOfBoundsException();
	}
	
	//TODO: Implementation
	public static short getTag(byte[] berTLVArray, short bTLVOff, byte[] berTagArray, short bTagOff) 
	{
		return 0;
	}
	
	// OK
	public BERTag getTag()
	{
		return theBERTag;
	}
	
	public short size() 
	{
		short value = 0;
		value += this.theBERTag.size();
		
		if (this instanceof PrimitiveBERTLV)
		{
			// length
			if (valueLength < 128) value++;
			else if (valueLength > 127) value += 2;
			else if (valueLength > 255) value += 3;
		     
			// value 
			value += getLength();
		}
		return value;
	}
	
	public short toBytes(byte[] outBuf, short bOff) 
	{
		return 0;
	}
	
	public static boolean verifyFormat(byte[] berTlvArray, short bOff, short bLen) 
		throws NullPointerException,
		ArrayIndexOutOfBoundsException
	{
		if (berTlvArray == null) throw new NullPointerException();	
		
		if ((short)(berTlvArray.length + bOff) >= bLen)
		{
			// tag
			if ((berTlvArray[bOff] & 0x1F) == 0x1F) 
				bOff += 2;  // 2 b
			else
				bOff++; //  1 b
			
			// length //9F 66 81 00 =?
			short checkBit = (short) (berTlvArray[bOff] & 0x80);
			
			if (checkBit == 0x00)
			{
				bOff++;
				if (((byte)(bLen - bOff)) == (byte)berTlvArray[(short)(bOff - 1)]) return true;
				else return false;
			}
			else
			{
				checkBit = (short) (berTlvArray[bOff] & 0x03);
				bOff++;
				switch (checkBit)
				{
					case 1:
						// 81 81
						if ((berTlvArray[(short)bOff] & 0x80) == 0x80) 
						{
							checkBit = (short) (berTlvArray[(short)(bOff)] & 0x00FF);
							if (checkBit < 128) return false;
							
							bOff++;
							
							if ((short)(berTlvArray.length - checkBit) != bOff) return false;
						}
						else return false;
						break;
						
					case 2:
						checkBit = MediatorFE.byteArrayToShort(
								new byte[] {berTlvArray[(short)(bOff + 1)], berTlvArray[(short)(bOff)]});
						
						if (checkBit < 256) return false;
						bOff += 2;
						if ((short)(berTlvArray.length - checkBit) != bOff) return false;
						break;
					
					default:
						return false;
				}
				
				return true;
			}
		}
		else 
			throw new ArrayIndexOutOfBoundsException();
	
	}
	
	public static byte[] getTLVLength(short value)
	{
		byte[] abc;

		if (value < 128) return new byte[] {(byte)value};
		
		else if (value > 127 && value < 256) return new byte[] {(byte) 0x81, (byte) ((byte) (value % 128) | 0x80)};
		
		else if (value > 255 && value < (short)0xFFFE)
		{
			abc = new byte[3];
			abc[0] = (byte) 0x82;
			Util.arrayCopy(MediatorFE.shortOnByteArray(value), (short)0, abc, (short)1, (short)2);			
			return abc;
		}
		else
		{
			throw new NullPointerException();	
		}
	}
	
	// 30 10 30 06 80 02 0
	public static BERTLV getInstance2(BERTLV tlv, byte[] bArray, short bOff, short bLen)
	{
		if (bLen == bOff) 	
			return tlv;
		else
		{
			BERTag berTag = BERTag.getInstance(bArray, bOff);
			
			if (berTag.isConstructed())
			{	
				if (tlv == null) 
					tlv = new ConstructedBERTLV((short)32);
				else
				{
					//tlv
				}
				
				
			}
			else
			{
				if (tlv == null) 
					tlv = new PrimitiveBERTLV(bLen);
				else
				{
					
				}
				// detect value length
				// init primitive BERTLV
			}
		}
		return tlv;
	}
}


